package com.xd.common.util;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.UUID;

/**
 * 该类是一个基于Redis实现的分布式锁，在需要使用分布式锁的地方，都可以使用该类来进行加锁和释放锁
 */
@Component
public class RedisDistributeLock {

    // 分布式锁前缀名
    private static final String REDIS_LOCK_PREFIX = "redis_lock_";

    // 成功标识
    private static final Long SUCCESS = 1L;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 加锁
     *
     * @param lockName       锁的名字，用于区分不同业务，最终加锁的key为分布式锁前缀+锁的名字
     * @param acquireTimeOut 请求超时时间，单位毫秒，一个线程不能一直尝试加锁，这样会导致请求的堆积，约定一个时间，在该时间内尝试加锁是有效的，否则不给予加锁
     * @param expireTime     分布式锁过期时间，单位毫秒，防止死锁的出现，需要设置一个兜底的过期时间
     * @return 唯一值，返回一个唯一值作为存入Redis的value值，用于解锁使用，这个值可以是UUID，数据库唯一ID或成的Code
     */
    public String getLock(String lockName, Long acquireTimeOut, Long expireTime) {
        // 生成当前线程的唯一值
        String uniqueValue = UUID.randomUUID().toString();
        // 设置当前线程可以获取锁的最大毫秒数
        long endTime = System.currentTimeMillis() + acquireTimeOut;
        // 如果当前系统时间还没有超过可以获取锁的最大时间，那么就尝试获取锁
        while (System.currentTimeMillis() < endTime) {
            // 使用lua脚本来实现redis原子性操作，这个命令表示设置key，如果key存在则设置失败返回0，否则成功返回1
            String luaScript = "if (redis.call('setnx', KEYS[1], ARGV[1]) == 1) then" +
                    "    return redis.call('expire', KEYS[1], ARGV[2]);" +
                    "else" +
                    "    return 0;" +
                    "end";

            RedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);
            // 尝试加锁
            Object result = redisTemplate.execute(redisScript, Collections.singletonList(REDIS_LOCK_PREFIX + lockName), uniqueValue, String.valueOf(expireTime / 1000));
            // 如果设置key成功，代表加锁成功
            if (SUCCESS.equals(result)) {
                return uniqueValue;
            }
            try {
                // 如果未拿到锁，那么立刻重新获取不是很好，稍等片刻再继续尝试
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 超过了当前线程可以获取锁的时间，那么直接返回
        return null;
    }

    /**
     * 解锁
     *
     * @param lockName    锁的名字，用于区分不同业务，最终加锁的key为分布式锁前缀+锁的名字
     * @param uniqueValue 唯一值，存入Redis时的value值，用于区分解锁的线程是否是加锁线程
     */
    public void releaseLock(String lockName, String uniqueValue) {
        if (!StringUtils.isEmpty(lockName) && !StringUtils.isEmpty(uniqueValue)) {
            // 判断下唯一值是否相同，目的是自己的锁自己解，不要把别的线程锁给解了
            String luaScript = "if (redis.call('get', KEYS[1]) == ARGV[1]) then" +
                    "    return redis.call('del', KEYS[1]);" +
                    "else" +
                    "    return 0;" +
                    "end";
            RedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);
            // 执行解锁lua脚本
            redisTemplate.execute(redisScript, Collections.singletonList(REDIS_LOCK_PREFIX + lockName), uniqueValue);
        }
    }

//    public int updateByPrimaryKeyStore(Integer id) {
//        System.out.println(new Date());
//
//        int update = 0;
//        //查一下商品库存
//        Goods goods = goodsMapper.selectByPrimaryKey(id);
//        String uniqueValue = null;
//        try {
//            //判断库存是否大于0
//            if (goods.getStore() > 0) {
//                //获取分布式锁
//                uniqueValue = redisDistributeLock.getLock("store",
//                        5000L, 8000L);
//                //再次查一下商品库存
//                Goods goods2 = goodsMapper.selectByPrimaryKey(id);
//                if (goods2.getStore() > 0) {
//                    update = goodsMapper.updateByPrimaryKeyStore(id);
//                    if (goods2.getStore() >= 0) {
//                        System.out.println("减库存成功，可以下订单");
//                        return update;
//                    }
//                }
//            }
//            System.out.println("减库存失败，不能下订单");
//            //返回结果
//            return update;
//        } finally {
//            //释放分布式锁
//            redisDistributeLock.releaseLock("store", uniqueValue);
//        }
//    }
}


